package com.ittx.dzt.meituan.util;


import com.google.gson.*;
import com.google.gson.internal.LinkedTreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 抖音签名工具类
 *
 * @author wushixiong
 * @since 2022/5/5 15:17
 */
public class MeituanSignUtil {

    private static final Gson CustomGson = new GsonBuilder()
            .registerTypeAdapter(LinkedTreeMap.class, newMapSerializer()) // 定制LinkedTreeMap序列化，确保所有key按字典序排序
            .registerTypeAdapter(Integer.class, newNumberSerializer()) // 定制数值类型的序列化，确保整数输出不带小数点
            .registerTypeAdapter(Long.class, newNumberSerializer()) // 同上
            .registerTypeAdapter(Double.class, newNumberSerializer()) // 同上
            .disableHtmlEscaping() // 禁用Html Escape，确保符号不转义：&<>='
            .create();

    /**
     * 序列化参数
     * 1、保证JSON所有层级上Key的有序性
     * 2、保证JSON的所有数值不带多余的小数点
     * 3、保证转义逻辑与这段代码一致
     *
     * @date 2022/5/5 15:28
     */
    public static String marshal(Object o) {
        String raw = CustomGson.toJson(o);
        Map<?, ?> m = CustomGson.fromJson(raw, LinkedTreeMap.class); // 执行反序列化，把所有JSON对象转换成LinkedTreeMap
        return CustomGson.toJson(m); // 重新序列化，保证JSON所有层级上Key的有序性
    }

    // 为LinkedTreeMap定制的序列化器
    public static JsonSerializer<LinkedTreeMap<?, ?>> newMapSerializer() {
        return new JsonSerializer<LinkedTreeMap<?, ?>>() {
            @Override
            public JsonElement serialize(LinkedTreeMap<?, ?> src, Type typeOfSrc, JsonSerializationContext context) {
                List<String> keys = src.keySet().stream().map(Object::toString).sorted().collect(Collectors.toList());
                JsonObject o = new JsonObject();
                for (String k : keys) {
                    o.add(k, context.serialize(src.get(k)));
                }
                return o;
            }
        };
    }

    // 为Number定制化的序列化器
    private static <T extends Number> JsonSerializer<T> newNumberSerializer() {
        return new JsonSerializer<T>() {
            @Override
            public JsonElement serialize(T number, Type type, JsonSerializationContext context) {
                if (number instanceof Integer) {
                    return new JsonPrimitive(number.intValue());
                }
                if (number instanceof Long) {
                    return new JsonPrimitive(number.longValue());
                }
                if (number instanceof Double) {
                    long longValue = number.longValue();
                    double doubleValue = number.doubleValue();
                    if (longValue == doubleValue) {
                        return new JsonPrimitive(longValue);
                    }
                }
                return new JsonPrimitive(number);
            }
        };
    }

    /**
     * 计算签名
     *
     * @param appKey    appkey
     * @param appSecret app密钥
     * @param method    请求API方法
     * @param timestamp 请求时间
     * @param paramJson 请求参数
     * @return 加密后的sign
     */
    public static String sign(String appKey, String appSecret, String method, long timestamp, String paramJson) {
        // 按给定规则拼接参数
        String paramPattern = new StringBuffer("app_key").append(appKey)
                .append("method").append(method)
                .append("param_json").append(paramJson)
                .append("timestamp").append(timestamp)
                .append("v2").toString();
//                "app_key" + appKey + "method" + method + "param_json" + paramJson + "timestamp" + timestamp + "v2";
        String signPattern = new StringBuffer(appSecret).append(paramPattern).append(appSecret).toString();
//                appSecret + paramPattern + appSecret;
        return hmac(signPattern, appSecret);
    }

    /**
     * 计算hmac
     *
     * @param plainText 加密文本
     * @param appSecret app密钥
     * @return 加密后的sign
     */
    public static String hmac(String plainText, String appSecret) {
        Mac mac;
        try {
            byte[] secret = appSecret.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");

            mac = Mac.getInstance("HmacSHA256");
            mac.init(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            return "";
        }
        byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] digest = mac.doFinal(plainBytes);
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

}
